package com.filldream.fastboot.common.util;

import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson2.JSONObject;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.*;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 微信支付V3工具
 * 请注意查看以下三个官方文档
 * 微信支付官方文档:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/direct-jsons/jsapi-prepay.html
 * 微信支付官方SDK使用说明：https://pay.weixin.qq.com/docs/merchant/sdk-tools/quickstart-java.html
 * SDK文档：https://github.com/wechatpay-apiv3/wechatpay-java
 * @author RickSun
 * @version 3.0
 * @data 2024-01-09
 */
@Slf4j
@Component
public class WxPayV3Util {

    @Value("${project.wxPayV3.config.merchantId}")
    private String merchantId;

    @Value("${project.wxPayV3.config.privateKeyPath}")
    private String privateKeyPath;

    @Value("${project.wxPayV3.config.merchantSerialNumber}")
    private String merchantSerialNumber;

    @Value("${project.wxPayV3.config.apiV3key}")
    private String apiV3key;

    @Value("${project.wxPayV3.config.appId}")
    private String appId;

    @Value("${project.wxPayV3.config.payNotifyUrl}")
    private String payNotifyUrl;

    @Value("${project.wxPayV3.config.refundNotifyUrl}")
    private String refundNotifyUrl;

    private Config config;

    private Config getConfig() {
        if (null == this.config) {
            synchronized (this) {
                if (null == this.config) {
                    this.config = new RSAAutoCertificateConfig.Builder()
                            .merchantId(merchantId)
                            .privateKeyFromPath(privateKeyPath)
                            .merchantSerialNumber(merchantSerialNumber)
                            .apiV3Key(apiV3key)
                            .build();
                }
                return config;
            }
        }
        return config;
    }

    //------------------请求----------------

    /**
     * 生成Native支付二维码
     * @param price 需支付金额，单位分
     * @param description   商品描述
     * @param outTradeNo    商家订单号
     * @return  返回支付二维码的字符串,前端需转换成二维码供用户扫码
     * https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html
     */
    public String genNativeQrCode( Integer price,String description,String outTradeNo ){
        //构建service
        NativePayService service = new NativePayService.Builder().config(getConfig()).build();
        Amount amount = new Amount();
        amount.setTotal(price);
        PrepayRequest request = new PrepayRequest();
        request.setAmount(amount);
        request.setAppid(appId);
        request.setMchid(merchantId);
        request.setDescription(description);
        request.setNotifyUrl(payNotifyUrl);
        request.setOutTradeNo(outTradeNo);
        //调用下单方法，得到应答
        PrepayResponse response = service.prepay(request);
        if( response != null ){
            return response.getCodeUrl();
        }
        return null;
    }

    /**
     * 查询订单详情
     * @param orderSn   微信支付系统生成的订单号 或 商家内部订单号
     * @param isMerchOrderNo    是否商家内部订单号 true-商家内部订单号 false-微信支付系统生成的订单号
     * @return Transaction
     */
    public Transaction queryOrder(String orderSn,Boolean isMerchOrderNo){
        NativePayService service = new NativePayService.Builder().config(getConfig()).build();
        if( isMerchOrderNo ){
            QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
            request.setMchid(merchantId);
            request.setOutTradeNo( orderSn );
            return service.queryOrderByOutTradeNo(request);
        }else{
            QueryOrderByIdRequest request = new QueryOrderByIdRequest();
            request.setTransactionId( orderSn );
            request.setMchid( merchantId );
            return service.queryOrderById(request);
        }
    }

    /**
     * 关闭订单
     * 以下是需要关单的场景
     * 商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付。
     * 系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     * @param outTradeNo    商户系统内部订单号
     */
    public void closeOrder( String outTradeNo ){
        NativePayService service = new NativePayService.Builder().config(getConfig()).build();
        CloseOrderRequest request = new CloseOrderRequest();
        request.setMchid(merchantId);
        request.setOutTradeNo(outTradeNo);
        service.closeOrder(request);
    }

    /**
     * 退款申请
     * @param refundRequest
     * @return
     */
    public Refund refunds( CreateRequest refundRequest ){
        RefundService service = new RefundService.Builder().config(getConfig()).build();
        //处理参数，采用覆盖形式
        if(StringUtils.isBlank(refundRequest.getNotifyUrl())){
            refundRequest.setNotifyUrl( refundNotifyUrl );
        }
        Refund refund = service.create(refundRequest);
        return refund;
    }

    /**
     * 查询退款
     * @param queryByOutRefundNoRequest
     * @return
     */
    public Refund queryRefunds( QueryByOutRefundNoRequest queryByOutRefundNoRequest ){
        RefundService service = new RefundService.Builder().config(getConfig()).build();
        Refund refund = service.queryByOutRefundNo(queryByOutRefundNoRequest);
        return refund;
    }



    //------------------回调----------------

    /**
     * 支付回调
     * @param request   HttpServletRequest请求
     * @return  Transaction
     */
    public Transaction payNotify(HttpServletRequest request){
        //1、统一获取RequestParam
        RequestParam requestParam = firstDealNotify(request,"微信支付");
        //2、初始化NotificationParser
        NotificationParser parser = new NotificationParser((NotificationConfig) getConfig());
        //3、支付回调：验签、解密并转换成Transaction
        Transaction transaction = parser.parse(requestParam, Transaction.class);
        return transaction;
    }

    /**
     * 退款回调
     * @param request   HttpServletRequest请求
     * @return  Transaction
     */
    public RefundNotification refundNotify(HttpServletRequest request){
        //1、统一获取RequestParam
        RequestParam requestParam = firstDealNotify(request,"微信退款");
        NotificationParser parser = new NotificationParser((NotificationConfig) getConfig());
        //2、退款通知回调，验签、解密并转换成 refundNotification
        RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);
        return refundNotification;
    }

    /**
     * 初步处理回调
     * @param request
     * @return
     */
    private RequestParam firstDealNotify(HttpServletRequest request,String remark){
        //1、获取微信应答请求头
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNumber = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");

        //2、获取微信应答请求体
        //是否使用需要close ServletInputStream 待定todo
        ServletInputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
        } catch (IOException e) {
            log.error("{}回调-获取请求体Body失败，异常信息：{}",remark, JSONObject.toJSONString(e.getMessage()));
            return null;
        }
        String body = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        if( StringUtils.isAnyBlank(body,timestamp,nonce,serialNumber,signature) ){
            log.error("{}回调-获取请求体及请求头失败，异常信息：timestamp:{},nonce:{},serialNumber:{},signature:{},body:{}",
                    remark,
                    timestamp,
                    nonce,
                    serialNumber,
                    signature,
                    body
            );
            return null;
        }

        //3、构造RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serialNumber)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .body(body)
                .build();

        return requestParam;
    }

}
